/*
 *
 * C++ MaJiang for 血流红中
 *
 * TuyooGame, 2021
 * zhouxin@tuyoogame.com
 *
 */
#include <cstring>
#include <inttypes.h>
#include "log.h"
#include "mj_tiles.h"

//初始化，供重复利用
Tiles::Tiles() {
}

//初始化并赋值牌列表，癞子列表，是否支持字顺
//字顺相关代码暂时不支持
Tiles::Tiles(vector<uint8_t> & t, vector<uint8_t> & l, bool is_zs) {
    assign(t, l, is_zs);
}

//赋值并重置各种状态数据
//用assign的模式，节省对象创建的开销，建议Tiles实例重复利用
bool
Tiles::assign(vector<uint8_t> & t, vector<uint8_t> & l, bool is_zs) {
    //sort(t.begin(), t.end());
    is_zishun = is_zs;

    //tiles vector...
    tiles_len = 0;
    size_t tlen = t.size();
    for(uint8_t i=0; i<tlen; i++) 
        if(getType(t[i]) != INVAILD) 
            tiles[tiles_len++] = t[i];

    //如果四个杠手牌有可能长度达到18
    if(tiles_len>18 or tiles_len<1) 
        return false;

    //laizi set...
    for(uint8_t i=0; i<l.size(); i++)
        laizi.insert(l[i]);

    //初始化counters...
    for(int i=0; i<3; i++) {
        counter[WAN+i].n = 0;
        counter[WAN+i].bucket_len = 9;
        memset(counter[WAN+i].bucket, 0, 9);
    }
    counter[FENG].n = 0;
    counter[FENG].bucket_len = 4;
    memset(counter[FENG].bucket, 0, 4);
    counter[JIAN].n = 0;
    counter[JIAN].bucket_len = 3;
    memset(counter[JIAN].bucket, 0, 3);
    counter_noz.n = 0;
    counter_noz.bucket_len = 34;
    memset(counter_noz.bucket, 0, 34);
    counter_z.n = 0;
    counter_z.bucket_len = 7;
    memset(counter_z.bucket, 0, 7);

    //统计计算放入counter...
    if(!countTiles())
        return false;

    //计算shape_noz和shape_z
    shape_z = shape(counter_z.bucket, counter_z.bucket_len);
    shape_noz = shape(counter_noz.bucket, counter_noz.bucket_len);
    return true;
}

Tiles::~Tiles() {
}

inline MJ_TILE_TYPE
Tiles::getType(uint8_t t) {
    if(t<=9  and t>=1)  return WAN;
    if(t<=19 and t>=11) return TIAO;
    if(t<=29 and t>=21) return TONG;
    if(t<=34 and t>=31) return FENG;
    if(t<=37 and t>=35) return JIAN;
    return INVAILD;
}

bool 
Tiles::countTiles(int one) {
    laizi_count = 0;
    MJ_COUNTER *cnz = &counter_noz, *cz = &counter_z, *cter;
    for(int i=0; i<tiles_len; i++) {
        uint8_t t = tiles[i];
        MJ_TILE_TYPE tt = getType(t);
        if(tt==INVAILD) return false;

        if(laizi.find(t) != laizi.end())
            laizi_count+=one;
        cter = &counter[tt];
        cter->n+=one;
        assert (t%10 != 0);
        uint8_t bktidx = tt==JIAN ? t%10 - 5 : t%10 - 1;
        cter->bucket[bktidx]+=one;

        if(tt!=FENG && tt!=JIAN) {
            cnz->n+=one;
            cnz->bucket[t-1]+=one;
            //python代码中允许出现大于4的情况
            //if(cnz->bucket[t-1]>4) return false;
        } else {
            cz->n+=one;
            cz->bucket[t%10-1]+=one;
        }
    }
    memset(count_all, 0, 32);
    memset(count_noz, 0, 32);
    for(uint8_t i=WAN; i<=JIAN; i++) {
        for(uint8_t j=0; j<counter[i].bucket_len; j++)
            count_all[counter[i].bucket[j]]++;
    }
    for(uint8_t i=0; i<counter_noz.bucket_len; i++) 
        count_noz[counter_noz.bucket[i]]++;
    return true;
}

bool
Tiles::isHu() {
    if(laizi_count==0 and !is_zishun) {
        int zicount2 = 0;
        for(uint64_t i=0; i<counter_z.bucket_len; i++) {
            uint8_t t = counter_z.bucket[i];
            if(t==2) zicount2++;
            if(!(t==0 || t==2 || t==3))
                return false;
        }
        uint64_t s = shape_noz;
        if(shape_z) {
            if(zicount2 > 1)
                return false;
            char tmp[64];
            snprintf(tmp, 64, "%" PRIu64 "0%" PRIu64, shape_noz, shape_z);
            s = atoll(tmp);
        }

        log_trace("shape = %" PRIu64 "\n", s);

        return ALL_SHAPE_SET.find(s) != ALL_SHAPE_SET.end();
    } else if(laizi_count==0 and is_zishun) {
        //pass...
    } else if(laizi_count!=0 and !is_zishun) {
        uint8_t lzn = laizi_count;
        if(tiles_len % 3 != 2) return false;
        bool jiang = false;
        for(int men=WAN; men<=TONG; men++) {
            uint64_t s = makeBuShape(counter[men].bucket, counter[men].bucket_len);
            for(int i=0; i<9; i++) {
                if(s % 10 == 0)
                    s /= 10;
                else
                    break;
            }
            if(s==0) continue;
            bool shun_part_hu = false;
            //tie: c++方法，用于解开tuple
            tie(shun_part_hu, lzn, jiang) = partBuShape(s, lzn, jiang);
            if(!shun_part_hu)
                return false;
        }
        return true;
    } else {
        //pass
    }
    return false;
}

vector<tuple<bool, uint8_t, bool>> 
Tiles::lastSplitShape(uint64_t s, uint8_t laizin, bool jiang) {
    vector<tuple<bool, uint8_t, bool>> res {};
    vector<uint8_t> zero_l {};
    char tmp[128];
    snprintf(tmp, 128, "%" PRIu64 , s);
    for(uint8_t i=0; i<strlen(tmp); i++) {
        if(tmp[i]=='0')
            zero_l.push_back(i);
    }

    log_trace(" >> last split shape...shape = %ld, zero_l.size = %ld\n", s, zero_l.size());
    for(uint8_t i=0; i<zero_l.size(); i++)
        log_trace("zero_l[%d]=%d\n", i, zero_l[i]);

    vector<uint64_t> shape_com {};
    for(uint8_t i=0; i<zero_l.size()+1; i++) {
        
        //python的itertools.combinations对0会迭代一次，并且输出(), 
        //c++的不会迭代，所以在这里补一下
        if(i == 0) 
            shape_com.push_back(s);

        for (auto&& m : combinations(zero_l, i)) {

            //组合放在zero_com里
            vector<uint8_t> zero_com {};
            for (auto&& n : m ) 
                zero_com.push_back(n);

            if(shape_com.size()!=0) {
                res.push_back(checkShapeComHu(shape_com, laizin, jiang));
            }
            shape_com.clear();

            if(zero_com.size()==0) {
                shape_com.push_back(s);
            }
            else {
                string ss = to_string(s);
                for(uint8_t k=0; k<zero_com.size(); k++) {
                    uint8_t start = (k==0?0:zero_com[k-1]+1);
                    shape_com.push_back(stoll(ss.substr(start, zero_com[k] - start)));
                    if(k+1 == (uint8_t)zero_com.size()) {
                        shape_com.push_back(stoll(ss.substr(zero_com[k]+1)));
                    }
                }
            }
        }
    }
    for(size_t i=0; i<shape_com.size(); i++) {
        vector<uint64_t> v1(shape_com.begin()+i, shape_com.end());
        vector<uint64_t> v2(shape_com.begin(), shape_com.begin()+i);
        v1.insert(v1.end(), v2.begin(), v2.end());
        res.push_back(checkShapeComHu(v1, laizin, jiang));
    }
    return res;
}

tuple<bool, uint8_t, bool> 
Tiles::checkShapeComHu(vector<uint64_t> &s, uint8_t laizin, bool jiang) {

    log_trace("check_shape_com_hu:input size=%lu, [", s.size());
    for(uint16_t i=0; i<s.size(); i++) 
        log_trace_n("%" PRIu64 ",", s[i]);
    log_trace_n("]\n");

    uint8_t lzx = 0;
    bool part_hu = true;
    for(uint64_t i=0; i<s.size(); i++) {
        uint64_t shape = s[i]; 
        if(MJHelper::searchPart(shape) != -1) {

            log_trace("search part match ... shape = %" PRIu64 "\n", shape);
            bool shape_jiang = shape % 3 == 2;
            if(!(shape_jiang and jiang)) {
                jiang = jiang or shape_jiang;
                continue;
            }
        }

        log_trace("search part not match ... shape = %" PRIu64 "\n", shape);

        if(shape == 4 and !jiang) {
            lzx++;
            jiang = true;
            continue;
        }
        //修复返回值没有赋给part_hu和jiang的bug
        uint8_t u;
        tie(part_hu, u, jiang) = noSplitShape(shape, laizin - lzx, jiang);
        if(!part_hu)
            break;
        lzx += u;
    }
    return make_tuple(part_hu, lzx, jiang);
}

//会遇到laizin为-1的情况，所以laizin参数改为int8_t，否则
//会导致死循环
tuple<bool, uint8_t, bool> 
Tiles::noSplitShape(uint64_t s, int8_t laizin, bool jiang) {
    bool part_hu = false;

    for(uint8_t i=0; i<laizin+1; i++) {

        log_trace("no_split_shape search bu...%u,%" PRIu64 "\n", i, s);
        if(MJHelper::searchBu(i, s) == -1)
            continue;

        bool have_jiang = (s+i%3 == 2);
        if(!(jiang and have_jiang)) {
            part_hu = true;
            return make_tuple(part_hu, i, jiang or have_jiang);
        }
    }
    return make_tuple(part_hu, 0, jiang);
}

tuple<bool, uint8_t, bool> 
Tiles::split00Shape(uint64_t shape, uint8_t laizin, bool jiang) {
    uint8_t laizi_x = 0;
    bool part_hu = false;
    vector<uint64_t> s00;
    string ss = to_string(shape);

    size_t pos = 0;
    string token;
    while ((pos = ss.find("00")) != string::npos) {
        token = ss.substr(0, pos);
        if(!(token.compare("0")==0 or token.compare("")==0))
            s00.push_back(stoll(token));
        ss.erase(0, pos + 2);
    }
    s00.push_back(stoll(ss));

    for(uint64_t i=0; i<s00.size(); i++) {
        uint8_t laizi_u = laizin - laizi_x;
        part_hu = false;
        auto lv = lastSplitShape(s00[i], laizi_u, jiang);
        for(auto t : lv) {
            bool px, jx;
            uint8_t lx;
            tie(px, lx, jx) = t;
            if(!px) continue;
            if(lx<=laizi_u) {
                part_hu = px;
                laizi_u = lx;
                jiang = jx;
            }
        }
        laizi_x += laizi_u;
        if(!part_hu)
            break;
    }
    return make_tuple(part_hu, laizin - laizi_x, jiang);
}

tuple<bool, uint8_t, bool>
Tiles::partBuShape(uint64_t shape, uint8_t laizin, bool jiang, bool zi) {
    bool part_hu = true;
    if(!zi) {
        if(MJHelper::searchPart(shape) != -1) {
            bool have_jiang = (shape%3 == 2);
            if(!(jiang and have_jiang))
                return make_tuple(part_hu, laizin, jiang or have_jiang);
        }
        return split00Shape(shape, laizin, jiang);
    } else {
    }
    return make_tuple(false, 0, false);
}



//把统计个数的桶，连接起来，转成一个十进制整数
//例如：AADDDEF -> vector(2,0,0,3,1,1,0,0,0) 
//返回：200311000
uint64_t 
Tiles::makeBuShape(uint8_t ts[], uint8_t tslen) {
    uint64_t s = 0;
    for(uint64_t i=0; i<tslen; i++) {
        uint8_t c = ts[i];
        s = s*10 + c;
    }
    return s;
}

//返回牌型数据，用1个0分割断开的牌
//例如：AADDDEF -> vector(2,0,0,3,1,1,0,0,0) 
//返回：20311
uint64_t 
Tiles::shape(uint8_t ts[], uint8_t tslen) {
    uint64_t s = 0;
    uint64_t limit = tslen - 1;
    for(uint64_t i=0; i<tslen; i++) {
        uint8_t c = ts[i];
        if(c || (i<limit && ts[i+1]))
            s = s*10 + c;
    }
    return s;
}

bool
Tiles::findTile(uint8_t t) {
    MJ_TILE_TYPE tt = getType(t);
    if(tt==INVAILD) return false;
    MJ_COUNTER *cter = &counter[tt];
    uint8_t bktidx = tt==JIAN ? t%10 - 5 : t%10 - 1;
    return cter->bucket[bktidx];
    /*for(uint8_t i=0; i<tiles_len; i++) {
        if(tiles[i] == t) 
            return true;
    }
    return false;*/
}

void
Tiles::getHands(vector<uint8_t> &v, uint8_t filter) {
    uint8_t m;
    for(m=0; m<tiles_len; m++) {
        if(tiles[m] == filter) 
            break;
        v.push_back(tiles[m]);
    }
    m++;
    for(;m<tiles_len; m++)
        v.push_back(tiles[m]);
}

void 
Tiles::getHandsNoZi(vector<uint8_t> &v, uint8_t filter) {
    for(uint8_t i=0; i<tiles_len; i++) {
        if(tiles[i]!=filter && tiles[i]!=35) 
            v.push_back(tiles[i]);
    }
    /*
    MJ_COUNTER *cnz = &counter_noz;
    for(uint8_t i=0; i<cnz->bucket_len; i++) {
        uint8_t tcnt = cnz->bucket[i];
        if(tcnt>0 && (i+1)!=filter) {
            for(uint8_t o=0; o<tcnt; o++)
                v.push_back(i+1); 
        }
    }
    */
}

bool 
Tiles::isLaiZi(uint8_t t) {
    return (laizi.find(t) != laizi.end());
}
